package com.ssm.subject.infra.basic.service.impl;

import com.ssm.subject.common.entity.PageResult;
import com.ssm.subject.common.enums.SubjectInfoTypeEnum;
import com.ssm.subject.infra.basic.entity.EsSubjectFields;
import com.ssm.subject.infra.basic.entity.SubjectInfoEs;
import com.ssm.subject.infra.basic.es.EsIndexInfo;
import com.ssm.subject.infra.basic.es.EsRestClient;
import com.ssm.subject.infra.basic.es.EsSearchRequest;
import com.ssm.subject.infra.basic.es.EsSourceData;
import com.ssm.subject.infra.basic.service.SubjectEsService;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;

@Service
@Log4j2
public class SubjectEsServiceImpl implements SubjectEsService {

    @Override
    public boolean insert(SubjectInfoEs subjectInfoEs) {
        EsSourceData esSourceData = new EsSourceData();
        esSourceData.setDocId(String.valueOf(subjectInfoEs.getDocId())); //指定docId
        Map<String, Object> data = convert2EsSourceData(subjectInfoEs);
        esSourceData.setData(data);
        return EsRestClient.insertDoc(getEsIndexInfo(), esSourceData);
    }

    @Override
    public PageResult<SubjectInfoEs> querySubjectList(SubjectInfoEs subjectInfoEs) {
        PageResult<SubjectInfoEs> pageResult = new PageResult<>();
        EsSearchRequest esSearchRequest = createSearchListQuery(subjectInfoEs);
        SearchResponse searchResponse = EsRestClient.searchWithTermQuery(getEsIndexInfo(), esSearchRequest);

        List<SubjectInfoEs> subjectInfoEsList = new ArrayList<>();
        SearchHits searchHits =  searchResponse.getHits(); //命中的全部数据

        //未命中
        if(searchHits == null || searchHits.getHits() == null) {
            pageResult.setPageNo(subjectInfoEs.getPageNo());
            pageResult.setPageSize(subjectInfoEs.getPageSize());
            pageResult.setRecords(subjectInfoEsList);
            pageResult.setTotal(0);
            return pageResult;
        }

        //命中数据 hits为文档数据集合
        SearchHit[] hits = searchHits.getHits();
        for(SearchHit hit : hits) {
            SubjectInfoEs infoEs = convertResult(hit); //hit数据转化为SubjectInfoEs实体
            if(Objects.nonNull(infoEs)) { //转换成功
                subjectInfoEsList.add(infoEs);
            }
        }

        pageResult.setPageNo(subjectInfoEs.getPageNo());
        pageResult.setPageSize(subjectInfoEs.getPageSize());
        pageResult.setRecords(subjectInfoEsList);
        pageResult.setTotal(Long.valueOf(searchHits.getTotalHits().value).intValue());

        return pageResult;
    }

    /**
     * hit数据转化为SubjectInfoEs实体
     */
    private SubjectInfoEs convertResult(SearchHit hit) {
        Map<String, Object> sourceAsMap = hit.getSourceAsMap(); //文档数据的map映射
        if(CollectionUtils.isEmpty(sourceAsMap)) {
            return null;
        }
        SubjectInfoEs result = new SubjectInfoEs();
        result.setSubjectId(MapUtils.getLong(sourceAsMap, EsSubjectFields.SUBJECT_ID));
        result.setDocId(MapUtils.getLong(sourceAsMap, EsSubjectFields.DOC_ID));
        result.setSubjectName(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_NAME));
        result.setSubjectAnswer(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_ANSWER));
        result.setSubjectType(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_TYPE));
        //查询结果中自带了每个文档的算分(1以内，转换为百分数：*100，保留两位小数，向上取整)
        result.setScore(new BigDecimal(String.valueOf(hit.getScore()))
                .multiply(new BigDecimal("100.00"))
                .setScale(2, BigDecimal.ROUND_HALF_UP));

        //处理高亮字段(将name，asnwer覆盖为高亮后的)
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        //获取题目名称高亮信息
        HighlightField subjectNameField = highlightFields.get(EsSubjectFields.SUBJECT_NAME);
        if(Objects.nonNull(subjectNameField)) {
            Text[] fragments = subjectNameField.getFragments(); //高亮片段集合
            StringBuilder subjectNameBuilder = new StringBuilder();
            for(Text fragment : fragments) {
                subjectNameBuilder.append(fragment.string());
            }
            result.setSubjectName(subjectNameBuilder.toString());
        }

        //获取题目答案高亮信息
        HighlightField subjectAnswerField = highlightFields.get(EsSubjectFields.SUBJECT_ANSWER);
        if(Objects.nonNull(subjectAnswerField)) {
            Text[] fragments = subjectAnswerField.getFragments();
            StringBuilder subjectAnswerBuilder = new StringBuilder();
            for(Text fragment : fragments) {
                subjectAnswerBuilder.append(fragment.string());
            }
            result.setSubjectAnswer(subjectAnswerBuilder.toString());
        }
        return result;
    }

    /**
     * 获取集群索引信息(控制在哪个集群，哪个索引下操作)
     */
    private EsIndexInfo getEsIndexInfo() {
        EsIndexInfo esIndexInfo = new EsIndexInfo();
        esIndexInfo.setClusterName("cf441bd98016");
        esIndexInfo.setIndexName("subject_index");
        return esIndexInfo;
    }

    /**
     * 将实体类转换成map数据
     */
    private Map<String, Object> convert2EsSourceData(SubjectInfoEs subjectInfoEs) {
        Map<String, Object> data = new HashMap<>();
        data.put(EsSubjectFields.SUBJECT_ID, subjectInfoEs.getSubjectId());
        data.put(EsSubjectFields.DOC_ID, subjectInfoEs.getDocId());
        data.put(EsSubjectFields.SUBJECT_NAME, subjectInfoEs.getSubjectName());
        data.put(EsSubjectFields.SUBJECT_ANSWER, subjectInfoEs.getSubjectAnswer());
        data.put(EsSubjectFields.SUBJECT_TYPE, subjectInfoEs.getSubjectType());
        data.put(EsSubjectFields.CREATE_USER, subjectInfoEs.getCreateUser());
        data.put(EsSubjectFields.CREATE_TIME, subjectInfoEs.getCreateTime());
        return data;
    }

    /**
     * 创建EsSearchRequest查询对象
     */
    private EsSearchRequest createSearchListQuery(SubjectInfoEs subjectInfoEs) {
        EsSearchRequest esSearchRequest = new EsSearchRequest();
        BoolQueryBuilder bq = new BoolQueryBuilder();

        MatchQueryBuilder subjectNameQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_NAME, subjectInfoEs.getKeyword());
        bq.should(subjectNameQueryBuilder); //将查询条件添加到布尔查询构造器中，should 表示或关系，为false时返回null
        bq.boost(2); //题目名称的相关性算分权重加2，优先展示题目名称匹配度高的。

        MatchQueryBuilder subjectAnswerQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_ANSWER, subjectInfoEs.getKeyword());
        bq.should(subjectAnswerQueryBuilder);

        //搜索只返回简答题，构造简答题查询条件
        MatchQueryBuilder subjectTypeQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_TYPE, SubjectInfoTypeEnum.BRIEF.getCode());
        bq.must(subjectTypeQueryBuilder); //必须为简答题，为must
        bq.minimumShouldMatch(1); //设置在should中至少需要匹配的条件数量，为1即至少匹配一个条件才返回结果。

        /**
         * 高亮构造器
         * field：* 对所有字段进行高亮处理
         * requireFieldMatch：false表示一个字段可以匹配多个高亮片段，默认为true即一个字段只能匹配一个高亮片段
         * preTags：为搜索结果的高亮部分设置了前后标签
         */
        HighlightBuilder highlightBuilder = new HighlightBuilder().field("*").requireFieldMatch(false);
        highlightBuilder.preTags("<span style = \"color:red\">");
        highlightBuilder.postTags("</span>");



        esSearchRequest.setBq(bq);
        esSearchRequest.setHighlightBuilder(highlightBuilder);
        esSearchRequest.setFields(EsSubjectFields.FIELD_QUERY); //指定搜索结果中包含的字段信息
        esSearchRequest.setFrom((subjectInfoEs.getPageNo() - 1) * subjectInfoEs.getPageSize()); //起始文档的位置(起始页数 - 1)*页容量，第一条文档为0
        esSearchRequest.setSize(subjectInfoEs.getPageSize());
        esSearchRequest.setNeedScroll(false); //不需要快照缓存
        return esSearchRequest;
    }
}





//package com.ssm.subject.infra.basic.service.impl;
//
//import com.alibaba.fastjson.JSON;
//import com.ssm.subject.infra.basic.entity.SubjectInfoEs;
//import com.ssm.subject.infra.basic.esRepo.SubjectEsRepository;
//import com.ssm.subject.infra.basic.service.SubjectEsService;
//import lombok.extern.log4j.Log4j2;
//import org.elasticsearch.index.query.QueryBuilders;
//import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
//import org.springframework.data.elasticsearch.core.IndexOperations;
//import org.springframework.data.elasticsearch.core.SearchHit;
//import org.springframework.data.elasticsearch.core.SearchHits;
//import org.springframework.data.elasticsearch.core.document.Document;
//import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
//import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
//import org.springframework.stereotype.Service;
//
//import javax.annotation.Resource;
//import java.util.ArrayList;
//import java.util.Date;
//import java.util.List;
//
//@Service
//@Log4j2
//public class SubjectEsServiceImpl implements SubjectEsService {
//
//    @Resource
//    private ElasticsearchRestTemplate elasticsearchRestTemplate;
//
//    @Resource
//    private SubjectEsRepository subjectEsRepository;
//
//    @Override
//    public void createIndex() {
//        IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(SubjectInfoEs.class);
//        indexOperations.create(); //创建索引
//        Document mapping = indexOperations.createMapping(SubjectInfoEs.class); //创建映射
//        indexOperations.putMapping(mapping);
//    }
//
//    @Override
//    public void addDocs() {
//        List<SubjectInfoEs> list = new ArrayList<>();
//        list.add(new SubjectInfoEs(1L, "redis是什么", "是一种缓存", "jichi", new Date()));
//        list.add(new SubjectInfoEs(2L, "mysql是什么", "是一种数据库", "jichi", new Date()));
//        subjectEsRepository.saveAll(list);
//    }
//
//    @Override
//    public void find() {
//        Iterable<SubjectInfoEs> all = subjectEsRepository.findAll();
//        for(SubjectInfoEs subjectInfoEs : all) {
//            log.info("subjectInfoEs:{}", JSON.toJSONString(subjectInfoEs));
//        }
//    }
//
//    @Override
//    public void search() {
//        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
//                .withQuery(QueryBuilders.matchQuery("subjectName", "redis"))
//                .build();
//        SearchHits<SubjectInfoEs> search = elasticsearchRestTemplate.search(nativeSearchQuery, SubjectInfoEs.class);
//        List<SearchHit<SubjectInfoEs>> searchHits = search.getSearchHits();
//        log.info("searchHits:{}", JSON.toJSONString(searchHits));
//    }
//}
